frontend/pages/e/[uuid]/details.tsx (view raw)
1import moment from 'moment';
2import Tooltip from '@mui/material/Tooltip';
3import IconButton from '@mui/material/IconButton';
4import Box from '@mui/material/Box';
5import Link from '@mui/material/Link';
6import Card from '@mui/material/Card';
7import Container from '@mui/material/Container';
8import TextField from '@mui/material/TextField';
9import Typography from '@mui/material/Typography';
10import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';
11import EventIcon from '@mui/icons-material/Event';
12import TuneIcon from '@mui/icons-material/Tune';
13import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
14import {useTheme} from '@mui/material/styles';
15import {DatePicker} from '@mui/x-date-pickers/DatePicker';
16import {PropsWithChildren, useState} from 'react';
17import {useTranslation} from 'react-i18next';
18import pageUtils from '../../../lib/pageUtils';
19import ShareEvent from '../../../containers/ShareEvent';
20import PlaceInput from '../../../containers/PlaceInput';
21import LangSelector from '../../../components/LangSelector';
22import usePermissions from '../../../hooks/usePermissions';
23import useEventStore from '../../../stores/useEventStore';
24import useToastStore from '../../../stores/useToastStore';
25import EventLayout, {TabComponent} from '../../../layouts/Event';
26import {
27 EventByUuidDocument,
28 useUpdateEventMutation,
29} from '../../../generated/graphql';
30
31interface Props {
32 eventUUID: string;
33 announcement?: string;
34}
35
36const Page = (props: PropsWithChildren<Props>) => {
37 return <EventLayout {...props} Tab={DetailsTab} />;
38};
39
40const DetailsTab: TabComponent<Props> = ({}) => {
41 const {t} = useTranslation();
42 const {
43 userPermissions: {canEditEventDetails},
44 } = usePermissions();
45 const theme = useTheme();
46 const [updateEvent] = useUpdateEventMutation();
47 const addToast = useToastStore(s => s.addToast);
48 const setEventUpdate = useEventStore(s => s.setEventUpdate);
49 const event = useEventStore(s => s.event);
50 const [isEditing, setIsEditing] = useState(false);
51
52 if (!event) return null;
53
54 const onSave = async e => {
55 try {
56 const {uuid, ...data} = event;
57 const {
58 id,
59 travels,
60 waitingPassengers,
61 __typename,
62 administrators,
63 passengers,
64 ...input
65 } = data;
66 await updateEvent({
67 variables: {
68 uuid,
69 eventUpdate: {
70 ...input,
71 },
72 },
73 refetchQueries: ['eventByUUID'],
74 });
75 setIsEditing(false);
76 } catch (error) {
77 console.error(error);
78 addToast(t('event.errors.cant_update'));
79 }
80 };
81
82 const modifyButton = isEditing ? (
83 <Tooltip
84 title={t('event.details.save')}
85 sx={{
86 position: 'absolute',
87 top: theme.spacing(2),
88 right: theme.spacing(2),
89 }}
90 >
91 <IconButton color="primary" onClick={onSave}>
92 <CheckCircleOutlineIcon />
93 </IconButton>
94 </Tooltip>
95 ) : (
96 <Tooltip
97 title={t('event.details.modify')}
98 sx={{
99 position: 'absolute',
100 top: theme.spacing(2),
101 right: theme.spacing(2),
102 }}
103 >
104 <IconButton color="primary" onClick={() => setIsEditing(true)}>
105 <TuneIcon />
106 </IconButton>
107 </Tooltip>
108 );
109
110 return (
111 <Box
112 sx={{
113 position: 'relative',
114 }}
115 >
116 <Container
117 sx={{
118 p: 4,
119 mt: 6,
120 mb: 11,
121 mx: 0,
122 [theme.breakpoints.down('md')]: {
123 p: 2,
124 },
125 }}
126 >
127 <Card
128 sx={{
129 position: 'relative',
130 maxWidth: '100%',
131 width: '350px',
132 p: 2,
133 }}
134 >
135 <Typography variant="h4" pb={2}>
136 {t('event.details')}
137 </Typography>
138 {canEditEventDetails() && modifyButton}
139 <Box pt={2} pr={1.5}>
140 <Typography variant="overline">{t('event.fields.name')}</Typography>
141 <Typography>
142 {isEditing ? (
143 <TextField
144 size="small"
145 fullWidth
146 value={event.name}
147 onChange={e => setEventUpdate({name: e.target.value})}
148 name="name"
149 id="EditEventName"
150 />
151 ) : (
152 <Typography id="EventName">
153 {event.name ?? t('event.fields.empty')}
154 </Typography>
155 )}
156 </Typography>
157 </Box>
158 <Box pt={2} pr={1.5}>
159 <Typography variant="overline">{t('event.fields.date')}</Typography>
160 {isEditing ? (
161 <Typography>
162 <DatePicker
163 slotProps={{
164 textField: {
165 size: 'small',
166 id: `EditEventDate`,
167 fullWidth: true,
168 placeholder: t('event.fields.date_placeholder'),
169 },
170 }}
171 format="DD/MM/YYYY"
172 value={moment(event.date)}
173 onChange={date =>
174 setEventUpdate({
175 date: !date ? null : moment(date).format('YYYY-MM-DD'),
176 })
177 }
178 />
179 </Typography>
180 ) : (
181 <Box position="relative">
182 <Typography id="EventDate">
183 {event.date
184 ? moment(event.date).format('DD/MM/YYYY')
185 : t('event.fields.empty')}
186 </Typography>
187 <EventIcon
188 color="action"
189 sx={{
190 position: 'absolute',
191 right: theme.spacing(-0.5),
192 top: 0,
193 }}
194 />
195 </Box>
196 )}
197 </Box>
198 <Box pt={2} pr={1.5}>
199 <Typography variant="overline">
200 {t('event.fields.address')}
201 </Typography>
202 {isEditing ? (
203 <PlaceInput
204 place={event.address}
205 latitude={event.latitude}
206 longitude={event.longitude}
207 onSelect={({place, latitude, longitude}) =>
208 setEventUpdate({
209 address: place,
210 latitude,
211 longitude,
212 })
213 }
214 />
215 ) : (
216 <Box position="relative">
217 <Typography id="EventAddress" sx={{pr: 3}}>
218 {event.address ? (
219 <Link
220 target="_blank"
221 rel="noreferrer"
222 href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
223 event.address
224 )}`}
225 onClick={e => e.preventDefault}
226 >
227 {event.address}
228 </Link>
229 ) : (
230 t('event.fields.empty')
231 )}
232 </Typography>
233 <PlaceOutlinedIcon
234 color="action"
235 sx={{
236 position: 'absolute',
237 right: theme.spacing(-0.5),
238 top: 0,
239 }}
240 />
241 </Box>
242 )}
243 </Box>
244 <Box pt={2} pr={1.5}>
245 <Typography variant="overline">
246 {t('event.fields.description')}
247 </Typography>
248 {isEditing ? (
249 <Typography>
250 <TextField
251 fullWidth
252 multiline
253 maxRows={4}
254 inputProps={{maxLength: 250}}
255 value={event.description || ''}
256 onChange={e => setEventUpdate({description: e.target.value})}
257 id={`EditEventDescription`}
258 name="description"
259 />
260 </Typography>
261 ) : (
262 <Typography id="EventDescription" sx={{pr: 3}}>
263 {event.description ?? t('event.fields.empty')}
264 </Typography>
265 )}
266 </Box>
267 <Box pt={2} pr={1.5}>
268 <Typography variant="overline">{t('event.fields.lang')}</Typography>
269 {isEditing ? (
270 <LangSelector
271 value={event.lang}
272 onChange={lang => setEventUpdate({lang})}
273 />
274 ) : (
275 <Typography id="EventLang" sx={{pr: 3}}>
276 {event.lang
277 ? t(`PROTECTED.languages.${event.lang}`)
278 : t('event.fields.empty')}
279 </Typography>
280 )}
281 </Box>
282 {!isEditing && (
283 <ShareEvent
284 title={`Caroster ${event.name}`}
285 sx={{width: '100%', mt: 2}}
286 />
287 )}
288 </Card>
289 </Container>
290 </Box>
291 );
292};
293
294export const getServerSideProps = pageUtils.getServerSideProps(
295 async (context, apolloClient) => {
296 const {uuid} = context.query;
297 const {host = ''} = context.req.headers;
298 let event = null;
299
300 // Fetch event
301 try {
302 const {data} = await apolloClient.query({
303 query: EventByUuidDocument,
304 variables: {uuid},
305 });
306 event = data?.eventByUUID?.data;
307 } catch (error) {
308 return {
309 notFound: true,
310 };
311 }
312
313 return {
314 props: {
315 eventUUID: uuid,
316 metas: {
317 title: event?.attributes?.name || '',
318 url: `https://${host}${context.resolvedUrl}`,
319 },
320 },
321 };
322 }
323);
324export default Page;